/*
 * Copyright (C) 2025 Huawei Technologies Co., Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.arkui.mediaquery

import androidx.annotation.MainThread
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.arkui.messenger.Messenger
import kotlinx.atomicfu.atomic

/**
 * Media query listener interface for observing media query state changes.
 */
interface MediaQueryListener {
    /**
     * Current match state of the query condition
     */
    val matches: State<Boolean>

    /**
     * The query condition string
     */
    val media: String

    /**
     * Register a callback for match state changes
     * @param type Event type (must be "change")
     * @param callback Function called when match state changes
     */
    fun on(type: String = "change", callback: (Boolean) -> Unit)

    /**
     * Unregister a callback for match state changes
     * @param type Event type (must be "change")
     * @param callback Specific callback to remove, or null to remove all
     */
    fun off(type: String = "change", callback: ((Boolean) -> Unit)? = null)

    /**
     * Destroy this listener and release resources
     */
    fun destroy()
}

/**
 * Manager for handling media query communication with ArkTS layer.
 *
 * Features:
 * - Reference counting for listener reuse
 * - Same condition shares one native listener
 * - Automatic cleanup when refCount reaches 0
 */
@Stable
class MediaQueryManager internal constructor(
    private val messenger: Messenger
) {
    // Use condition string as key for listener reuse
    private val listenersByCondition = mutableMapOf<String, SharedMediaQueryListener>()

    init {
        registerMessageHandlers()
    }

    private fun registerMessageHandlers() {
        // Register handler for media query change notifications from ArkTS
        messenger.onReceive("compose.mediaquery:changed") { message ->
            try {
                // Parse JSON: {"id":"...", "condition":"...", "matches":true/false, "media":"..."}
                val condition = parseJsonString(message, "condition")
                val matches = parseJsonBoolean(message, "matches")

                if (condition != null && matches != null) {
                    listenersByCondition[condition]?.updateMatches(matches)
                }
                ""
            } catch (e: Exception) {
                e.printStackTrace()
                ""
            }
        }
    }

    /**
     * Create a media query listener for the given condition.
     * If a listener for the same condition already exists, it will be reused.
     *
     * @param condition Media query condition string (e.g., "(orientation: landscape)")
     * @return MediaQueryListener instance
     */
    @MainThread
    fun matchMediaSync(condition: String): MediaQueryListener {
        // Check if listener for this condition already exists
        listenersByCondition[condition]?.let { shared ->
            shared.addReference()
            return shared.createProxy()
        }

        // Create new shared listener
        val response = messenger.send("compose.mediaquery:matchMediaSync", condition)

        if (response.isNullOrEmpty()) {
            // Return a dummy listener that always returns false
            return createDummyListener(condition)
        }

        // Check for error
        parseJsonString(response, "error")?.let { error ->
            // Return a dummy listener instead of throwing
            return createDummyListener(condition)
        }

        val listenerId = parseJsonString(response, "id")
            ?: return createDummyListener(condition)
        val matches = parseJsonBoolean(response, "matches") ?: false
        val media = parseJsonString(response, "media") ?: condition

        val sharedListener = SharedMediaQueryListener(
            condition = condition,
            listenerId = listenerId,
            initialMatches = matches,
            media = media,
            messenger = messenger,
            onDestroy = { listenersByCondition.remove(condition) }
        )

        listenersByCondition[condition] = sharedListener
        return sharedListener.createProxy()
    }

    /**
     * Get number of active listeners (for debugging)
     */
    fun getListenerCount(): Int = listenersByCondition.size

    /**
     * Clean up all resources
     */
    @MainThread
    fun destroy() {
        listenersByCondition.values.toList().forEach { it.forceDestroy() }
        listenersByCondition.clear()
    }

    /**
     * Create a dummy listener that always returns false (for error cases)
     */
    private fun createDummyListener(condition: String): MediaQueryListener {
        return DummyMediaQueryListener(condition)
    }

    // JSON parsing utilities
    private fun parseJsonString(json: String, key: String): String? {
        return try {
            val pattern = """"$key"\s*:\s*"([^"]+)"""".toRegex()
            val matchResult = pattern.find(json)
            matchResult?.groupValues?.get(1)
        } catch (e: Exception) {
            null
        }
    }

    private fun parseJsonBoolean(json: String, key: String): Boolean? {
        return try {
            val pattern = """"$key"\s*:\s*(true|false)""".toRegex()
            val matchResult = pattern.find(json)
            matchResult?.groupValues?.get(1)?.toBoolean()
        } catch (e: Exception) {
            null
        }
    }
}

/**
 * Shared listener implementation (internal)
 * Multiple proxy listeners can share one native listener
 */
private class SharedMediaQueryListener(
    val condition: String,
    val listenerId: String,
    initialMatches: Boolean,
    override val media: String,
    private val messenger: Messenger,
    private val onDestroy: () -> Unit
) : MediaQueryListener {

    private val _matches = mutableStateOf(initialMatches)
    override val matches: State<Boolean> = _matches

    private val refCount = atomic(1)
    // Callbacks list - operations should be called from main thread
    private val callbacks = mutableListOf<(Boolean) -> Unit>()
    private var isListening = false

    /**
     * Increment reference count. Must be called from main thread.
     */
    fun addReference() {
        refCount.incrementAndGet()
    }

    /**
     * Decrement reference count and destroy if reaches 0. Must be called from main thread.
     */
    fun removeReference() {
        if (refCount.decrementAndGet() <= 0) {
            forceDestroy()
        }
    }

    /**
     * Force destroy this listener. Must be called from main thread.
     */
    fun forceDestroy() {
        callbacks.clear()
        val destroyMessage = """{"condition":"$condition"}"""
        messenger.send("compose.mediaquery:destroy", destroyMessage)
        onDestroy()
    }

    /**
     * Update match state and notify callbacks. Must be called from main thread.
     */
    fun updateMatches(newMatches: Boolean) {
        if (_matches.value != newMatches) {
            _matches.value = newMatches
            // Create a copy to avoid concurrent modification
            callbacks.toList().forEach { it(newMatches) }
        }
    }

    /**
     * Create a proxy listener for this shared listener
     */
    fun createProxy(): MediaQueryListener {
        return ProxyMediaQueryListener(this)
    }

    /**
     * Add callback. Must be called from main thread.
     */
    fun addCallback(callback: (Boolean) -> Unit) {
        callbacks.add(callback)
        ensureListening()
    }

    /**
     * Remove callback. Must be called from main thread.
     */
    fun removeCallback(callback: (Boolean) -> Unit) {
        callbacks.remove(callback)
    }

    private fun ensureListening() {
        if (!isListening && callbacks.isNotEmpty()) {
            val onMessage = """{"condition":"$condition"}"""
            messenger.send("compose.mediaquery:on", onMessage)
            isListening = true
        }
    }

    // MediaQueryListener interface methods (for shared listener itself)
    override fun on(type: String, callback: (Boolean) -> Unit) {
        if (type == "change") {
            addCallback(callback)
        }
    }

    override fun off(type: String, callback: ((Boolean) -> Unit)?) {
        if (type == "change") {
            if (callback == null) {
                callbacks.clear()
            } else {
                removeCallback(callback)
            }
        }
    }

    override fun destroy() {
        removeReference()
    }
}

/**
 * Proxy listener for external use
 * Each caller gets their own proxy with independent callback management
 */
private class ProxyMediaQueryListener(
    private val shared: SharedMediaQueryListener
) : MediaQueryListener {

    override val matches: State<Boolean> = shared.matches
    override val media: String = shared.media

    private val myCallbacks = mutableListOf<(Boolean) -> Unit>()

    override fun on(type: String, callback: (Boolean) -> Unit) {
        if (type == "change") {
            myCallbacks.add(callback)
            shared.addCallback(callback)
        }
    }

    override fun off(type: String, callback: ((Boolean) -> Unit)?) {
        if (type == "change") {
            if (callback == null) {
                myCallbacks.forEach { shared.removeCallback(it) }
                myCallbacks.clear()
            } else {
                myCallbacks.remove(callback)
                shared.removeCallback(callback)
            }
        }
    }

    override fun destroy() {
        // Remove all my callbacks from shared listener
        myCallbacks.forEach { shared.removeCallback(it) }
        myCallbacks.clear()
        // Decrease reference count
        shared.removeReference()
    }
}

/**
 * Dummy listener for error cases (always returns false)
 */
private class DummyMediaQueryListener(
    override val media: String
) : MediaQueryListener {
    private val _matches = mutableStateOf(false)
    override val matches: State<Boolean> = _matches

    override fun on(type: String, callback: (Boolean) -> Unit) {
        // No-op
    }

    override fun off(type: String, callback: ((Boolean) -> Unit)?) {
        // No-op
    }

    override fun destroy() {
        // No-op
    }
}

/**
 * CompositionLocal for MediaQueryManager
 */
val LocalMediaQueryManager = staticCompositionLocalOf<MediaQueryManager> {
    error("No MediaQueryManager provided")
}
